package microcredit.gui.table;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.ImageIcon;
import javax.swing.table.AbstractTableModel;

import microcredit.gui.table.BeanTable.SelectionMode;
import microcredit.gui.util.ExceptionDialog;
import microcredit.model.util.ModelUtils;

public final class BeanTableModel<E> extends AbstractTableModel {

	private static final long serialVersionUID = 1L;
	private List<E> beans;
	private Set<E> selected;
	private E singleSelectedElement;
	
	private EnumBeanTableModel<E, Enum<?>> delegate;
	private SelectionMode selectionMode = SelectionMode.DEFAULT;
	
	@SuppressWarnings("unchecked")
	public BeanTableModel(EnumBeanTableModel<E, ?> delegate) {
		beans = new ArrayList<E>();
		selected = new HashSet<E>();
		this.delegate = (EnumBeanTableModel<E, Enum<?>>)delegate;
		delegate.setDelegate(this);
	}
	
	public void setSelectionMode(SelectionMode selectionMode) {
		SelectionMode oldSelectionMode = this.selectionMode;
		this.selectionMode = selectionMode;
		if (oldSelectionMode != selectionMode) {
			fireTableStructureChanged();
		}
	}
	
	private void setElementSelected(E element, boolean selected) {
		if (selectionMode == SelectionMode.SINGLE) {
			if (selected) {
				singleSelectedElement = element;
			} else {
				singleSelectedElement = null;
			}
		} else if (selectionMode == SelectionMode.MULTI) {
			if (selected) {
				this.selected.add(element);
			} else {
				this.selected.remove(element);
			}
		}
	}
	
	public void setSelectedElements(List<E> selectedElements) {
		List<E> previousSelected = getSelectedElements();
		for (E e : previousSelected) {
			setElementSelected(e, false);
		}
		if (selectedElements != null) {
			for (E e : selectedElements) {
				setElementSelected(e, true);
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	public List<E> getSelectedElements() {
		if (selectionMode == SelectionMode.SINGLE) {
			if (singleSelectedElement != null) {
				List<E> list = new ArrayList<E>(1);
				list.add(singleSelectedElement);
				return list;
			}
			return Collections.emptyList();
		} else if (selectionMode == SelectionMode.MULTI) {
			return (List<E>)Arrays.asList(selected.toArray(new Object[selected.size()]));
		}
		return Collections.emptyList();
	}
	
	private boolean isElementSelected(E element) {
		if (selectionMode == SelectionMode.SINGLE) {
			return ModelUtils.equals(singleSelectedElement, element);
		} else if (selectionMode == SelectionMode.MULTI) {
			return selected.contains(element);
		}
		return false;
	}
	
	public SelectionMode getSelectionMode() {
		return selectionMode;
	}
	
	int getStartIndex() {
		if (getSelectionMode() == SelectionMode.DEFAULT) {
			return 1;
		} else {
			return 2;
		}
	}
	
	int getSelectIndex() {
		if (getSelectionMode() == SelectionMode.DEFAULT) {
			return -1;
		} else {
			return (getStartIndex() -2);
		}
	}
	
	boolean isSelectionColumn(int column) {
		return column == getSelectIndex();
	}
	
	boolean isIconColumn(int column) {
		return column == (getStartIndex() -1);
	}
	
	@SuppressWarnings("unchecked")
	public void load(List<E> beans) {
		if (beans == null) {
			this.beans = (List<E>)Collections.EMPTY_LIST;
		} else {
			this.beans = beans;
		}
		fireTableDataChanged();
	}
	
	public int getRowCount() {
		return beans.size();
	}

	public List<E> getBeans() {
		return beans;
	}
	
	public Enum<?> convertToEnum(int columnIndex) {
		if (columnIndex < getStartIndex()) {
			return null;
		}
		return delegate.getEnumValues()[columnIndex-getStartIndex()];
	}
	
	public int convertToIndex(Enum<?> enumValue) {
		return getStartIndex() + Arrays.asList(delegate.getEnumValues()).indexOf(enumValue);
	}
	
	public Enum<?> getSortColumn() {
		return delegate.getSortColumn();
	}
	
	@Override
	public final void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		if (isSelectionColumn(columnIndex)) {
			Boolean booleanValue = (Boolean)aValue;
			if (booleanValue == null) {
				booleanValue = Boolean.FALSE;
			}
			setElementSelected(beans.get(rowIndex), booleanValue.booleanValue());
		} else if (convertToEnum(columnIndex) != null) {
			delegate.setValue(beans.get(rowIndex), aValue, convertToEnum(columnIndex));
		}
	}
	
	@Override
	public final Class<?> getColumnClass(int columnIndex) {
		if (isSelectionColumn(columnIndex)) {
			return Boolean.class;
		} else if (convertToEnum(columnIndex) == null) {
			return String.class;
		}
		return delegate.getValueClass(convertToEnum(columnIndex));
	}
	
	@Override
	public final String getColumnName(int column) {
		if (isSelectionColumn(column)) {
			return "Select";
		}
		if (convertToEnum(column) == null) {
			return "";
		}
		return delegate.getName(convertToEnum(column));
	}
	
	public Color getForegroundColor(int row, int column) {
		if (convertToEnum(column) == null) {
			return null;
		}
		return delegate.getForeground(getBeans().get(row), convertToEnum(column));
	}
	
	public Object getFormat(int row, int column) {
		if (convertToEnum(column) == null) {
			return null;
		}
		return delegate.getFormat(getBeans().get(row), convertToEnum(column));
	}
	
	public Color getBackgroundColor(int row, int column) {
		if (convertToEnum(column) == null) {
			return delegate.getBackground(getBeans().get(row), null);
		}
		return delegate.getBackground(getBeans().get(row), convertToEnum(column));
	}
	
	public boolean isBold(int row, int column) {
		if (convertToEnum(column) == null) {
			return false;
		}
		return delegate.isBold(getBeans().get(row), convertToEnum(column));
	}
	
	public int getPreferredWidth(int column) {
		if (convertToEnum(column) == null) {
			return -1;
		}
		return delegate.getWidth(convertToEnum(column));
	}
	
	public ImageIcon getRowIcon() {
		return delegate.getRowIcon();
	}
	
	@Override
	public final boolean isCellEditable(int rowIndex, int columnIndex) {
		if (isSelectionColumn(columnIndex)) {
			return true;
		} else if (convertToEnum(columnIndex) == null) {
			return false;
		}
		return delegate.isEditable(getBeans().get(rowIndex), convertToEnum(columnIndex));
	}

	public final int getColumnCount() {
		return delegate.getEnumValues().length + getStartIndex();
	}
	
	public final Object getValueAt(int rowIndex, int columnIndex) {
		if (isSelectionColumn(columnIndex)) {
			return isElementSelected(getBeans().get(rowIndex));
		} else if (isIconColumn(columnIndex)) {
			return delegate.getRowIcon(getBeans().get(rowIndex));
		} else if (convertToEnum(columnIndex) == null) {
			return null;
		}
		return delegate.getValue(getBeans().get(rowIndex), convertToEnum(columnIndex));
	}

}
